package com.zkh.myutils.image;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpSession;

import com.zkh.myutils.io.log.Logger;
import com.zkh.myutils.utils.StringUtils;

/**
 * 验证码类<br><br>
 * 如果使用中文验证码，可使用自定义字体。（字体文件系统只会加载一次）<br><br><b>
 * 自定义中文字体方法：<br>
 * 1、将字体文件命名为：custom.ttf<br>
 * 2、将custom.ttf文件放在classes目录下
 * @author zkh
 *
 */
public final class VerifyCode {
	//用户自定义字段
	private static Font customFont;
	//是否已经加载过字体（防止没有自定义字体时，无法判断是否已经加载过而导致每次都重新加载字体）
	private static boolean isLoadCustomFont = false;
	//获取随机对象
	private static Random random = new Random();
	
	//验证码类型
	private CodeType type;
	//验证码大小
	private int width;
	private int height;
	//验证码数据
	private String code;
	//字体库
	private String[] fontlib = new String[4];
	//干扰元素
	private boolean lineInterrupt = true;	//线干扰
	private boolean dotInterrupt = true;	//点干扰
	
	//初始化代码块
	{
		//验证码库
		fontlib[0] = "0123456789";
		fontlib[1] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
		fontlib[2] = "abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ023456789";
		fontlib[3] = "秦恭夏剖剧俸倩债借值倚俺倾倒俱倡倍倦健倔泰珠班素蚕顽夏烈殊顾捕振捏捉损紧党哄逞";
	}
	
	/**
	 * 设置干扰元素
	 * @param line 是否是设置线干扰，false设置点干扰。
	 * @param open 是否开启干扰，false不开启
	 */
	public void setInterrupt(boolean line, boolean open) {
		if(line) {
			this.lineInterrupt = open;
		}else {
			this.dotInterrupt = open;
		}
	}
	
	/**
	 * 设置字体库
	 * @param fontlib
	 */
	public void setFontlib(String fontlib) {
		this.fontlib[type.index] = fontlib;
	}
	
	/**
	 * 初始化验证码大小，类型为英文和数字混合
	 * @param width 验证码宽度
	 * @param height 验证码高度
	 */
	public VerifyCode(int width,int height){
		this.width = width;
		this.height = height;
		this.type = CodeType.LETTER_NUMBER;
	}

	/**
	 * 构造方法，初始化验证码大小，验证码类型
	 * @param width 验证码宽度
	 * @param height 验证码高度
	 * @param type 验证码类型 。通过枚举类CodeType获取。
	 */
	public VerifyCode(int width,int height,CodeType type){
		this.width = width;
		this.height = height;
		this.type = type;
		//中文字体，加载自定义字体
		if(type==CodeType.CHINESE && !isLoadCustomFont) {
			//检测是否存在自定义字段(检测class目录下的custom.ttf文件)
			ClassLoader loader = VerifyCode.class.getClassLoader();
			//添加日志
			Logger.info("【验证码】获取加载器：" + loader);
			//获取URL
			InputStream is = null;
			//加载器为空或未取到资源
			if(loader==null || (is=loader.getResourceAsStream("/custom.ttf"))==null) {
				is = VerifyCode.class.getResourceAsStream("/custom.ttf");
			}
			//加载到资源
			if(is!=null) {
				Logger.info("【验证码】加载到字体文件");
				try {
					customFont = Font.createFont(Font.TRUETYPE_FONT, is);
				} catch (FontFormatException | IOException e) {
					throw new RuntimeException("【验证码】加载字体时发生异常", e);
				}
			}else {
				Logger.info("【验证码】未加载到字体文件");
			}
			//检测过字体标识
			isLoadCustomFont = true;
		}
	}

	/**
	 * 获取验证码图片
	 */
	public BufferedImage create(){
		
		//创建BufferedImage对象
		BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		
		//将验证码数据写入验证码
		write(bi.createGraphics());
		
		return bi;
	}
	
	/**
	 * 获取验证码文本，建议直接调用saveToSession方法
	 * @return 验证码信息
	 */
	public String getCode(){
		return this.code;
	}
	
	/**
	 * 保存验证码文本，必须在create()之后调用。<br />
	 * 执行<code>session.setAttribute("myframe-verifycode",code)</code>
	 */
	public void saveToSession(HttpSession session){
		//将验证码保存到session
		session.setAttribute("myframe-verifycode", this.code);
	}
	
	/**
	 * 清除session
	 * @param session
	 */
	public static void clearSession(HttpSession session) {
		session.removeAttribute("myframe-verifycode");
	}
	
	/**
	 * 验证输入的验证码是否正确
	 * @param session
	 * @param target
	 * @return true表示验证码正确，false表示验证码错误
	 */
	public static boolean checkCode(HttpSession session, String target){
		//获取验证码
		String verifycode = (String) session.getAttribute("myframe-verifycode");
		//验证码是否正确
		return StringUtils.isNotEmpty(verifycode) && verifycode.equals(target);
	}
	
	/**
	 * 输出图片
	 * @param bi
	 * @param outputStream
	 */
	public void output(BufferedImage bi,OutputStream outputStream){
		try {
			ImageIO.write(bi, "png", outputStream);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 获取随机颜色
	 * @param start 颜色开始值
	 * @param end 颜色结束值
	 * @return Color对象
	 */
	private Color getColor(int start,int end){
		//颜色范围
		int range = end-start;
		//R值
		int r = random.nextInt(range)+start;
		//G值
		int g = random.nextInt(range)+start;
		//B值
		int b = random.nextInt(range)+start;

		//返回颜色对象
		return new Color(r,g,b);
	}
	
	private void write(Graphics2D g){
		//设置背景和字体样式
		setStyle(g);
		
		//验证码长度
		int len = fontlib[type.index].length();
		//开始位置
		int start;
		//验证码信息
		StringBuilder code = new StringBuilder();
		//临时保存
		String temp;
		//获取字符
		for(int i=0;i<4;i++){
			start = random.nextInt(len-1);
			temp = fontlib[type.index].substring(start,start+1);
			
			//随即获取字体样式
			getRandomFont(g);
			
			g.setColor(getColor(0,100));	
			g.drawString(temp, width/4*i+random.nextInt(10), height/2+random.nextInt(height/2));
			code.append(temp);
		}
		this.code = code.toString();
		//设置干扰元素
		setInterrupt(g);
	}
	
	/**
	 * 获取随机字体样式
	 */
	private void getRandomFont(Graphics2D g){
		//字号数组
		int[] fontSizes = {20, 21, 22, 23, 24};
		//随即获取字形
		int fontSize = fontSizes[random.nextInt(fontSizes.length)];

		//字形数组
		int[] fontStyles = {Font.ITALIC, Font.PLAIN, Font.BOLD, Font.ITALIC+Font.BOLD};
		//随即获取字形
		int fontStyle = fontStyles[random.nextInt(fontStyles.length)];
		
		//字体对象
		Font font;
		//不存在自定义字段
		if(customFont==null) {
			//字体数组
			String[] fontFamilys = {"宋体", "华文楷体", "黑体", "华文新魏", "华文隶书", "微软雅黑", "楷体_GB2312"};
			//随机获取字体
			String fontFamily = fontFamilys[random.nextInt(fontFamilys.length)];

			//保存字段
			font = new Font(fontFamily,fontStyle,fontSize);
		}else {
			//使用自定义字段
			font = customFont.deriveFont((float) fontSize).deriveFont(fontStyle);
		}
		
		//设置字体
		g.setFont(font);
	}
	
	/**
	 * 设置背景颜色
	 * @param g Graphics2D对象
	 */
	private void setStyle(Graphics2D g){
		//设置背景
		g.setColor(getColor(200,250));
		
		g.fillRect(0, 0, width, height);

		//消除文字的锯齿
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
	}
	
	/**
	 * 设置干扰元素
	 * @param g
	 */
	private void setInterrupt(Graphics2D g){
		if(lineInterrupt) {
			//线干扰元素，4条干扰线
			for(int i=0;i<4;i++){
				g.setColor(getColor(100,150));
				g.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width), random.nextInt(height));
			}
		}
		if(dotInterrupt) {
			//点干扰元素，200个点
			for(int i=0;i<200;i++){
				g.setColor(getColor(150,200));
				int x = random.nextInt(width);
				int y = random.nextInt(height);
				g.drawLine(x, y, x, y);
			}
		}
	}
}
